#include "concepts.h"
#include "file.h"
#include "fstream.h"
#include "http_data.h"
#include "http_server.h"
#include "http_status.h"
#include "json.hpp"
#include "socket.h"
#include "task.h"
#include "uri.h"
#include "uring_loop.h"
#include "websocket.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <coroutine>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <functional>
#include <iostream>
#include <memory>
#include <netinet/in.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>

namespace co_async {
HttpResponse::HttpResponse(
    int status, std::unordered_map<std::string, std::string> &&header,
    std::string &&body)
    : m_status(status), m_headers(std::move(header)), m_body(std::move(body)) {
  if (status == 404) {
    m_headers["Content-Type"] = "text/html; charset=utf-8";
    m_body =
        R"(<!DOCTYPE html><html lang="en"><head><title>404 Not Found</title></head><body><h1>404 Not Found</h1></body></html>)";
  }
}
Task<> HttpResponse::write_into(FileStream &sock) {
  using namespace std::string_view_literals;
  co_await sock.puts("HTTP/1.1 "sv);
  co_await sock.puts(std::to_string(m_status));
  co_await sock.putchar(' ');
  co_await sock.puts(HTTP_STATUS_CODE[m_status]);
  co_await sock.puts("\r\n"sv);
  for (auto const &[k, v] : m_headers) {
    co_await sock.puts(k);
    co_await sock.puts(": "sv);
    co_await sock.puts(v);
    co_await sock.puts("\r\n"sv);
  }
  if (m_body.empty()) {
    co_await sock.puts("\r\n"sv);
  } else {
    co_await sock.puts("content-length: "sv);
    co_await sock.puts(std::to_string(m_body.size()));
    co_await sock.puts("\r\n\r\n"sv);
    co_await sock.puts(m_body);
  }
  co_await sock.flush();
}

bool HttpResponse::is_ws() const {
  return m_headers.find("sec-websocket-accept") != m_headers.end();
}

HttpRequest::HttpRequest(){};
HttpRequest::HttpRequest(auto uri) : m_uri(uri) {}
Task<bool> HttpRequest::read_from(FileStream &sock) {
  using namespace std::string_view_literals;
  auto line = co_await sock.getline("\r\n"sv);
  if (!co_await parse_url(line)) {
    co_return false;
  }

  while (true) {
    line = co_await sock.getline("\r\n"sv);
    if (line.empty()) {
      break;
    }
    if (!co_await parse_header(line)) {
      co_return false;
    }
  }

  auto it = m_header.find("content-length");
  if (it != m_header.end()) {
    m_body = co_await sock.getn(std::stoll(it->second));
  }
  it = m_header.find("content-type");
  if (it == m_header.end()) {
    co_return true;
  }
  m_params = Http_Content_Handler[it->second](m_body);

  co_return true;
}

const std::string &HttpRequest::get_path() const { return m_uri.get_path(); }
const std::unordered_map<std::string, std::string> &
HttpRequest::get_queries() const {
  return m_uri.get_params();
}
Method::MethodType HttpRequest::get_method() { return m_method; }
const std::unordered_map<std::string, std::string> &
HttpRequest::get_header() const {
  return m_header;
}

const njson &HttpRequest::get_params() { return m_params; }
const std::string &HttpRequest::get_body() const { return m_body; }

std::unordered_map<std::string, std::function<njson(const std::string &)>>
    HttpRequest::Http_Content_Handler = {
        {"application/x-www-form-urlencoded",
         [](const std::string &body) {
           auto uri = Uri::FromString("?" + body);
           return uri.get_params();
         }},
        {"application/json", [](const std::string &body) {
           njson result;
           try {
             result = njson::parse(body);
           } catch (std::exception &e) {
             return njson::object();
           }
           return result;
         }}};

Task<bool> HttpRequest::parse_url(const std::string &line) {
  auto pos = line.find(' ');
  if (pos == line.npos || pos == line.size() - 1) [[unlikely]] {
    co_return false;
  }
  auto method = line.substr(0, pos);
  std::transform(method.begin(), method.end(), method.begin(), ::tolower);
  m_method = Method::GetMethod(method);
  auto pos2 = line.find(' ', pos + 1);
  if (pos2 == line.npos || pos2 == line.size() - 1) [[unlikely]] {
    co_return false;
  }
  m_uri = Uri::FromString(line.substr(pos + 1, pos2 - pos - 1));
  co_return true;
}

Task<bool> HttpRequest::parse_header(const std::string &line) {
  auto pos = line.find(':');
  if (pos == line.npos || pos == line.size() - 1 || line[pos + 1] != ' ')
      [[unlikely]] {
    co_return false;
  }
  auto key = line.substr(0, pos);
  std::transform(key.begin(), key.end(), key.begin(), ::tolower);
  m_header[std::move(key)] = line.substr(pos + 2);
  co_return true;
}

HttpServer::HttpServer(std::string_view addr, uint16_t port)
    : m_addr(addr), m_port(port) {}

// void route(std::string path, Handler task) { m_routes[path] = task; }
void HttpServer::post(const std::string &path, Handler task) {
  m_handlers[Method::POST][path] = task;
}
void HttpServer::get(const std::string &path, Handler task) {
  m_handlers[Method::GET][path] = task;
}

void HttpServer::set_static_dir(std::string_view dir) {
  if (!std::filesystem::is_directory(dir)) {
    throw std::runtime_error("dir not exist");
  }
  for (const auto &entry : std::filesystem::recursive_directory_iterator(dir)) {

    if (entry.is_regular_file()) {
      auto ext = entry.path().extension();
      std::string path = "/";
      path.append(std::filesystem::relative(entry.path(), dir).string());
      auto it = HTTP_CONTENT_TYPE.find(ext);
      std::string content_type = "application/octet-stream";
      if (it != HTTP_CONTENT_TYPE.end()) {
        content_type = it->second;
      }
      get(path,
          [file = entry.path().string(),
           content_type](const HttpRequest &req) -> Task<HttpResponse> {
            int fd =
                co_await uring_openat(loop, AT_FDCWD, file.data(), O_RDONLY);
            auto fs = FileStream(FileHandle(fd));
            auto html = co_await fs.getline(256);
            co_return {200, {{"Content-Type", content_type}}, std::move(html)};
          });
    }
  }
}

WebSocket &HttpServer::update(const std::string &path) {
  auto ws = std::make_shared<WebSocket>(path);
  m_ws_map[path] = ws;
  WebSocket::Update(*this, path);
  return *ws;
}

void HttpServer::serve() {
  auto t = run();
  t.get_handle().resume();
  while (!t.get_handle().done()) {
    loop.run();
  }
  t.operator co_await().await_resume();
}

Task<void, HttpPromise> HttpServer::handle_connection() {
  FileStream fconn{
      (co_await GetCoroutine<HttpPromise>{}).promise().m_conn.move()};
  HttpRequest req{};
  if (!co_await req.read_from(fconn)) {
    HttpResponse resp{
        400,
    };
    co_await resp.write_into(fconn);
    co_return;
  }
  std::cout << req.get_path() << std::endl;
  if (req.get_method() == Method::UNKNOWN) {
    HttpResponse resp{
        405,
    };
    co_await resp.write_into(fconn);
    co_return;
  }
  auto it = m_handlers[req.get_method()].find(req.get_path());
  UnInit<HttpResponse> uresp;
  if (it != m_handlers[req.get_method()].end()) {
    uresp.put(co_await it->second(req));
  } else {
    uresp.put(404);
  }
  auto resp = uresp.move();
  co_await resp.write_into(fconn);
  if (!resp.is_ws()) {
    co_return;
  }
  auto it_ws = m_ws_map.find(req.get_path());
  if (it_ws == m_ws_map.end()) {
    co_return;
  }
  co_await it_ws->second->add_endpoint(std::move(fconn))->handle_ws();
}
Task<> HttpServer::run() {
  co_async::SocketServer serv =
      co_await co_async::SocketServer::CreateServer(m_addr.data(), m_port);
  std::cout << "正在监听 127.0.0.1:8848" << std::endl;
  while (true) {
    auto conn = co_await serv.server_accept();
    sockaddr_in paddr;
    socklen_t paddr_len = sizeof(paddr);
    co_async::check_error(
        getpeername(conn.get_fd(), (sockaddr *)&paddr, &paddr_len));
    char peer_ip[INET_ADDRSTRLEN];
    inet_ntop(serv.get_addr().get_family(), &paddr.sin_addr, peer_ip,
              sizeof(peer_ip));
    std::cout << peer_ip << ':' << ntohs(paddr.sin_port) << ' ';
    auto handler = handle_connection();
    handler.m_handle.promise().m_conn.put(std::move(conn));
    uring_enqueue(loop, handler);
  }
}
} // namespace co_async